3

转载请注明出处: 翻译: Spring Cloud Feign使用文档

Why Feign and not X?

Feign使用诸如JerseyCXF之类的工具来实现ReSTSOAP服务的java客户端, 此外, Feign允许你在http库(如: Apache HC)之上编写自己的代码. 通过自定义解码器(decoders)和错误处理(error handing), Feign可以用最小的开销和最少的代码将你的代码关联到任何基于文本的http接口(http APIS),

How does Feign work?

Feign是通过将注解(annotations)转换成模板请求来实现它的功能的, Feign可以将请求参数直接应用到这些模板上. 尽管Feign只支持基于文本的接口, 但同样的它能显著地简化系统的方方面面, 如请求重放等, 此外, Feign也可以使你的单元测试更加简单.

Java Version Campatibility

Feign 10.x及以上的版本是基于Java 8构建的, 且应该同样支持Java 9、10、11, 如果你需要在JDK 6的版本上使用的话, 请使用Feign 9.x版本.

Basics

下面的代码是适配Retrofit示例的用法:

interface GitHub {
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);
}

public static class Contributor {
  String login;
  int contributions;
}

public class MyApp {
  public static void main(String... args) {
    GitHub github = Feign.builder()
                         .decoder(new GsonDecoder())
                         .target(GitHub.class, "https://api.github.com");
  
    // Fetch and print a list of the contributors to this library.
    List<Contributor> contributors = github.contributors("OpenFeign", "feign");
    for (Contributor contributor : contributors) {
      System.out.println(contributor.login + " (" + contributor.contributions + ")");
    }
  }
}

Interface Annotations

Feign的注解定义了接口与底层http客户端功能之间的约定, 默认情况下各个注解的约定含义如下:

Annotation Interface Target Usage
@RequestLine 接口 定义请求的HttpMethodUriTemplate. 模板中可以使用大括号包围的表达式({expression}), 表达式的值由@Param对应参数的注解值提供.
@Param 参数 定义模板变量, 变量的值应该由名字相对应的表达式提供.
@Headers 方法、Type 定义HeaderTemplate; 使用@Param注解的值解析对应的表达式. 当该注解应用在Type上时, 该模板会被应用到每一个请求上. 当该注解应用在方法上时, 该模板仅会被应用到对应的方法上.
@QueryMap 参数 将键值对类型的Map、POJO展开成地址上的请求参数(query string)
@HeaderMap 参数 将键值对类型的Map展开成请求头Http Headers.
@Body 方法 定义与UriTemplateHeaderTemplate类似的模板(Template), 该模板可以使用@Param的注解值解析对应的表达式

Templates and Expressions

Feign支持由URI Template - RFC 6570定义的简单字符串(Level 1)表达式, 表达式的值从相关方法上对应@Param注解提供, 示例如下:

public interface GitHub {
  
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List<Contributor> getContributors(@Param("owner") String owner, @Param("repo") String repository);
  
  class Contributor {
    String login;
    int contributions;
  }
}

public class MyApp {
  public static void main(String[] args) {
    GitHub github = Feign.builder()
                         .decoder(new GsonDecoder())
                         .target(GitHub.class, "https://api.github.com");
    
    /* The owner and repository parameters will be used to expand the owner and repo expressions
     * defined in the RequestLine.
     * 
     * the resulting uri will be https://api.github.com/repos/OpenFeign/feign/contributors
     */
    github.contributors("OpenFeign", "feign");
  }
}

表达式必须使用大括号({})包裹着, 并且支持使用冒号(:)分隔的正则表达式来限定表达式的值. 如限定上述例子的owner参数的值必须是字母: {owner:[a-zA-Z]*}.

Request Parameter Expansion

RequestLineQueryMap遵循 URI Template - RFC 6570规范对一级模板(Level 1 templates)的规定:

  • 未被解析的值将会被忽略.
  • 所有未编码或者通过@Param注解标记为已编码(encoded)的字符和变量值都使用pct编码(pct-encoded).

可以从Advanced Usage一节查看更多示例.

What about slashes? /

默认情况下, @RequestLine@QueryMap模板不会对正斜杠/进行编码, 如果需要默认对其进行编码的话, 可以将@RequestLinedecodeSlash属性值设置为false.

What about plus? +

根据URI规范, +可以使用在URI地址和请求参数(query segments)这两个部分上, 然而在请求参数(query)上对该符号的处理却有可能不一致, 在一些遗留的系统上, +会被解析成一个空白符(space). 对此, Feign采用现代系统对+的解释, 不会将+认为是一个空白符(space), 并将请求参数上的+编码为%2B.

如果你希望将+当成空白符(space), 那么请直接使用一个空格 或者直接将其编码为%20.

Custom Expansion

@Param注解有一个可选的参数expander可以用来控制单个参数的展开行为(expansion), 该属性的值必须指向一个实现了Expander接口的类:

public interface Expander {
    String expand(Object value);
}

对该方法的返回值的处理与上述规则相同, 如果返回值是null或者是一个空字符串, 那么该值会被忽略. 如果返回值不是使用pct编码(pct-encoded)的, 将会自动转换成pct编码. 可以从 Custom @Param Expansion 一节查看更多示例.

Request Headers Expansion

@HeadersHeaderMap模板对 Request Parameter Expansion 一节阐述的规则做以下修改, 并遵循之:

  • 未被解析的值将会被忽略, 但如果解析到一个空的值(empty header value), 那么对应的请求头会被移除.
  • 不会对请求头使用pct编码(pct-encoding).

可以从Headers一节查看示例.

关于@Param参数和参数名需要注意的点

无论是在@RequestLine@QueryMap@BodyTemplate还是@Headers上的表达式, 只要表达式内的变量名字相同, 那么它们的值也必然相同. 如下面的例子, contentType的值会同时被解析到请求头(header)和路径(path)上:

public interface ContentService {
  @RequestLine("GET /api/documents/{contentType}")
  @Headers("Accept: {contentType}")
  String getDocumentByType(@Param("contentType") String type);
}

当你在设计你的接口的一定要牢记这一点.

Reuqest Body Expansion

Body模板对 Request Parameter Expansion 一节阐述的规则做以下修改, 并遵循之:

  • 未被解析的值将会被忽略.
  • 展开的值在被解析到请求体之前不会经过Encoder处理.
  • 必须指定Content-Type请求头, 可以从 Body Templates一节查看示例.

Customization

你可以在很多地方对Feign进行定制. 比如, 你可以使用Feign.builder()对自定义的组件构建API接口:

interface Bank {
  @RequestLine("POST /account/{id}")
  Account getAccountInfo(@Param("id") String id);
}

public class BankService {
  public static void main(String[] args) {
    Bank bank = Feign.builder().decoder(
        new AccountDecoder())
        .target(Bank.class, "https://api.examplebank.com");
  }
}

Multiple Interfaces

Feign客户以对使用Target<T>(默认是HardCodedTarget<T>)定义的对象生成多个API接口, 这样你可以在执行前动态发现服务或者对请求进行装饰.

例如, 下面的代码可以实现为从身份服务中获取当前url授权令牌(auth token), 然后设置到每个请求上:

public class CloudService {
  public static void main(String[] args) {
    CloudDNS cloudDNS = Feign.builder()
      .target(new CloudIdentityTarget<CloudDNS>(user, apiKey));
  }
  
  class CloudIdentityTarget extends Target<CloudDNS> {
    /* implementation of a Target */
  }
}

Examples

Feign包含了GitHubWikipedia的客户端示例代码, 在实践中也可以参考这些项目, 尤其是example daemon.


Integrations

Feign在设计上就希望能够和其他开源项目很好的整合到一起, 我们也很乐于将你喜欢的模块添加进来.

Gson

Gson包含了和JSON接口相关的编码(GsonEncoder)、解码器(GsonDecoder), 将它将它用到Feign.Builder的方式如下:

public class Example {
  public static void main(String[] args) {
    GsonCodec codec = new GsonCodec();
    GitHub github = Feign.builder()
                         .encoder(new GsonEncoder())
                         .decoder(new GsonDecoder())
                         .target(GitHub.class, "https://api.github.com");
  }
}

Jackson

Jackson包含了和JSON接口相关的编码(JacksonEncoder)、解码器(JacksonDecoder), 将它将它用到Feign.Builder的方式如下:

public class Example {
  public static void main(String[] args) {
      GitHub github = Feign.builder()
                     .encoder(new JacksonEncoder())
                     .decoder(new JacksonDecoder())
                     .target(GitHub.class, "https://api.github.com");
  }
}

Sax

SaxDecoder提供了可以与普通JVM和Android环境兼容的方式解析XML文本, 下面的例子展示了如何使用:

public class Example {
  public static void main(String[] args) {
      Api api = Feign.builder()
         .decoder(SAXDecoder.builder()
                            .registerContentHandler(UserIdHandler.class)
                            .build())
         .target(Api.class, "https://apihost");
    }
}

JAXB

JAXB包含了和XML接口相关的编码器(JAXBEncoder)、解码器(JAXBEncoder), 将它将它用到Feign.Builder的方式如下:

public class Example {
  public static void main(String[] args) {
    Api api = Feign.builder()
             .encoder(new JAXBEncoder())
             .decoder(new JAXBDecoder())
             .target(Api.class, "https://apihost");
  }
}

JAX-RS

JAXRSContract使用JAX-RS规范提供的标准覆盖了对注解的处理, 目前实现的是1.1版的规范, 示例如下:

interface GitHub {
  @GET @Path("/repos/{owner}/{repo}/contributors")
  List<Contributor> contributors(@PathParam("owner") String owner, @PathParam("repo") String repo);
}

public class Example {
  public static void main(String[] args) {
    GitHub github = Feign.builder()
                       .contract(new JAXRSContract())
                       .target(GitHub.class, "https://api.github.com");
  }
}

OkHttp

OkHttpClient直接将Feign的http请求直接交由OkHttp处理, 后者实现了SPDY协议和提供了更好的网络控制能力.

OkHttp整合到Feign中需要你把OkHttp模块放到classpath下, 然后做如下配置:

public class Example {
  public static void main(String[] args) {
    GitHub github = Feign.builder()
                     .client(new OkHttpClient())
                     .target(GitHub.class, "https://api.github.com");
  }
}

Ribbon

RibbonClient会覆盖Feign客户端的URL解析, 以实现由Ribbon提供的智能路由和弹性能力.

RibbonFeign整合需要你将url中的主机名(host)部分替换成Ribbon客户端名. 例如Ribbon客户端明为myAppProd:

public class Example {
  public static void main(String[] args) {
    MyService api = Feign.builder()
          .client(RibbonClient.create())
          .target(MyService.class, "https://myAppProd");
  }
}

Java 11 Http2

Http2Client直接将Feign的http请求交给Java11 New HTTP/2 Client处理, 后者实现了HTTP/2协议.

要将New HTTP/2 ClientFeign整合使用, 你需要使用Java SDK 11, 并做如下配置:

GitHub github = Feign.builder()
                     .client(new Http2Client())
                     .target(GitHub.class, "https://api.github.com");

Hystrix

HystrixFeign实现了由Hystrix提供的断路器功能.

要将HystrixFeign整合, 你需要将Hystrix模块放到classpath下, 并使用HystrixFeign:

public class Example {
  public static void main(String[] args) {
    MyService api = HystrixFeign.builder().target(MyService.class, "https://myAppProd");
  }
}

SOAP

SOAP包含了XML接口相关的编码器(SOAPEncoder)、解码器(SOAPDecoder).

该模块通过JAXB和SOAPMessage实现了对SOAP Body的编码和解码的支持, 通过将SOAPFault包装秤javax.xml.ws.soap.SOAPFaultException实现了对SOAPFault解码的功能, 因此, 对于SOAPFault的处理, 你只需要捕获SOAPFaultException.

使用示例如下:

public class Example {
  public static void main(String[] args) {
    Api api = Feign.builder()
         .encoder(new SOAPEncoder(jaxbFactory))
         .decoder(new SOAPDecoder(jaxbFactory))
         .errorDecoder(new SOAPErrorDecoder())
         .target(MyApi.class, "http://api");
  }
}

如果SOAP Faults的响应使用了表示错误的状态码(4xx, 5xx, …)的话, 那么你还需要添加一个SOAPErrorDecoder.

SLF4J

SLF4JModule实现了将Feign的日志重定向到SLF4J, 这允许你很容易的就能使用你想用的日志后端(Logback、Log4J等).

要将SLF4JFeign整合, 你需要将SLF4J模块和对应的日志后端模块放到classpath下, 并做如下配置:

public class Example {
  public static void main(String[] args) {
    GitHub github = Feign.builder()
                     .logger(new Slf4jLogger())
                     .target(GitHub.class, "https://api.github.com");
  }
}

Decoders

Feign.builder()允许你手动指定额外的配置, 如配置如何对响应进行解析.

如果你接口定义的方法的返回值是除了ResponseStringbyte[]void之外的类型, 那么你必须配置一个非默认的Decoder.

下面的代码展示了如何配置使用feign-gson对JSON解码:

public class Example {
  public static void main(String[] args) {
    GitHub github = Feign.builder()
                     .decoder(new GsonDecoder())
                     .target(GitHub.class, "https://api.github.com");
  }
}

如果你想在对响应进行解码之前先对其做处理的话, 你可以使用mapAndDecode方法, 下面的代码展示了对一个jsonp响应的处理, 在将响应交给JSON解码器之前, 需要先对jsonp做处理:

public class Example {
  public static void main(String[] args) {
    JsonpApi jsonpApi = Feign.builder()
                         .mapAndDecode((response, type) -> jsopUnwrap(response, type), new GsonDecoder())
                         .target(JsonpApi.class, "https://some-jsonp-api.com");
  }
}

Encoders

将一个请求体发送到服务器的最简单的办法是定义一个POST请求方法, 该方法的参数类型是Stringbyte[], 且参数上不带任何注解, 并且你可能还需要设置Content-Type请求头(如果没有的话):

interface LoginClient {
  @RequestLine("POST /")
  @Headers("Content-Type: application/json")
  void login(String content);
}

public class Example {
  public static void main(String[] args) {
    client.login("{\"user_name\": \"denominator\", \"password\": \"secret\"}");
  }
}

而通过配置Encoder, 你可以发送一个类型安全的请求体, 下面的例子展示了使用feign-gson扩展来实现编码:

static class Credentials {
  final String user_name;
  final String password;

  Credentials(String user_name, String password) {
    this.user_name = user_name;
    this.password = password;
  }
}

interface LoginClient {
  @RequestLine("POST /")
  void login(Credentials creds);
}

public class Example {
  public static void main(String[] args) {
    LoginClient client = Feign.builder()
                              .encoder(new GsonEncoder())
                              .target(LoginClient.class, "https://foo.com");
    
    client.login(new Credentials("denominator", "secret"));
  }
}

@Body templates

使用@Body注解的模板会使用@Param注解的值来展开模板内部的表达式, 对于POST请求你可能还需要设置Content-Type请求头(如果没有的话):

interface LoginClient {

  @RequestLine("POST /")
  @Headers("Content-Type: application/xml")
  @Body("<login \"user_name\"=\"{user_name}\" \"password\"=\"{password}\"/>")
  void xml(@Param("user_name") String user, @Param("password") String password);

  @RequestLine("POST /")
  @Headers("Content-Type: application/json")
  // json curly braces must be escaped!
  @Body("%7B\"user_name\": \"{user_name}\", \"password\": \"{password}\"%7D")
  void json(@Param("user_name") String user, @Param("password") String password);
}

public class Example {
  public static void main(String[] args) {
    client.xml("denominator", "secret"); // <login "user_name"="denominator" "password"="secret"/>
    client.json("denominator", "secret"); // {"user_name": "denominator", "password": "secret"}
  }
}

Headers

Feign支持在api上为每个请求设置请求头, 也支持为每个客户端的请求设置请求头, 你可以根据实际场景进行选择.

Set headers using apis

对于那些明确需要设置某些请求头的接口的情况, 适用于将请求头的定义作为接口的一部分.

静态配置的请求头可以通过在接口上使用@Headers注解设置:

@Headers("Accept: application/json")
interface BaseApi<V> {
  @Headers("Content-Type: application/json")
  @RequestLine("PUT /api/{key}")
  void put(@Param("key") String key, V value);
}

也可以在方法上的@Headers使用变量展开动态指定请求头的内容:

public interface Api {
   @RequestLine("POST /")
   @Headers("X-Ping: {token}")
   void post(@Param("token") String token);
}

有时候, 对于同一个接口或客户端的请求头, 其键和值可能会随着不同的方法调用而发生变化, 且不可预知(例如: 自定义元数据请求头字段"x-amz-meta-"或"x-goog-meta-"), 此时可以在接口上声明一个Map参数, 并使用@HeaderMap注解将Map的内容设置为对应请求的请求头:

public interface Api {
   @RequestLine("POST /")
   void post(@HeaderMap Map<String, Object> headerMap);
}

上述的几个方法都可以在接口上指定请求的请求头, 且不需要在构造时对Feign客户端做任何的定制.

Setting headers per target

当同一个接口的请求需要针对不同的请求对象(endpoints)配置不同的请求头, 或者需要对同一个接口的每个请求都定制其请求头时, 可以在Feign客户端上使用RequestInterceptorTarget来设置请求头.

使用RequestInterceptor设置请求头的例子可以在Request Interceptor一节中查看示例.

使用Target设置请求头的示例如下:

  static class DynamicAuthTokenTarget<T> implements Target<T> {
    public DynamicAuthTokenTarget(Class<T> clazz,
                                  UrlAndTokenProvider provider,
                                  ThreadLocal<String> requestIdProvider);
    
    @Override
    public Request apply(RequestTemplate input) {
      TokenIdAndPublicURL urlAndToken = provider.get();
      if (input.url().indexOf("http") != 0) {
        input.insert(0, urlAndToken.publicURL);
      }
      input.header("X-Auth-Token", urlAndToken.tokenId);
      input.header("X-Request-ID", requestIdProvider.get());

      return input.request();
    }
  }
  
  public class Example {
    public static void main(String[] args) {
      Bank bank = Feign.builder()
              .target(new DynamicAuthTokenTarget(Bank.class, provider, requestIdProvider));
    }
  }

上述方法的最终效果取决于你对RequestInterceptorTarget内部的实现, 可以通过这种方法对每个Feign客户端的所有接口调用设置请求头. 这在一些场景下是非常有用的, 如对每个Feign客户端的所有请求设置认证令牌authentication token. 这些方法是在接口调用者所在的线程中执行的(译者注: 需要注意线程安全), 因此请求头的值可以是在调用时根据上下文动态地设置. 例如, 可以根据不同的调用线程, 从ThreadLocal里读取不同的数据设置请求头.

Advanced usage

Base Apis

大多数情况下服务的接口都遵循相同的约定. Feign使用单继承的方式来实现, 比如下面的例子:

interface BaseAPI {
  @RequestLine("GET /health")
  String health();

  @RequestLine("GET /all")
  List<Entity> all();
}

你可以通过继承的方式来拥有BaseAPI的接口, 并实现其他特定的接口:

interface CustomAPI extends BaseAPI {
  @RequestLine("GET /custom")
  String custom();
}

很多时候, 接口对资源的表示也是一致的, 因此, 也可以在基类的接口中使用泛型参数:

@Headers("Accept: application/json")
interface BaseApi<V> {

  @RequestLine("GET /api/{key}")
  V get(@Param("key") String key);

  @RequestLine("GET /api")
  List<V> list();

  @Headers("Content-Type: application/json")
  @RequestLine("PUT /api/{key}")
  void put(@Param("key") String key, V value);
}

interface FooApi extends BaseApi<Foo> { }

interface BarApi extends BaseApi<Bar> { }

Logging

你可以通过为Feign客户端设置Logger来记录其http日志, 最简单的实现如下:

public class Example {
  public static void main(String[] args) {
    GitHub github = Feign.builder()
                     .decoder(new GsonDecoder())
                     .logger(new Logger.JavaLogger().appendToFile("logs/http.log"))
                     .logLevel(Logger.Level.FULL)
                     .target(GitHub.class, "https://api.github.com");
  }
}

Request Interceptors

如果你需要跨Feign客户端对所有请求都做修改, 那么你可以配置RequestInterceptor来实现. 例如, 如果你是请求的一个代理, 那么你可能会需要设置X-Forwarded-For请求头:

static class ForwardedForInterceptor implements RequestInterceptor {
  @Override public void apply(RequestTemplate template) {
    template.header("X-Forwarded-For", "origin.host.com");
  }
}

public class Example {
  public static void main(String[] args) {
    Bank bank = Feign.builder()
                 .decoder(accountDecoder)
                 .requestInterceptor(new ForwardedForInterceptor())
                 .target(Bank.class, "https://api.examplebank.com");
  }
}

另一个常见的使用拦截器的场景是授权, 比如使用内置的BasicAuthRequestInterceptor:

public class Example {
  public static void main(String[] args) {
    Bank bank = Feign.builder()
                 .decoder(accountDecoder)
                 .requestInterceptor(new BasicAuthRequestInterceptor(username, password))
                 .target(Bank.class, "https://api.examplebank.com");
  }
}

Custom @Param Expansion

使用@Param注解的参数会用其toString()方法展开获得参数值, 也可以通过制定一个自定义的Param.Expander来控制. 如对日期的格式化:

public interface Api {
  @RequestLine("GET /?since={date}") Result list(@Param(value = "date", expander = DateToMillis.class) Date date);
}

Dynamic Query Parameters

可以通过对Map类型的参数加上QueryMap注解, 将Map的内容构造成查询参数(query parameters):

public interface Api {
  @RequestLine("GET /find")
  V find(@QueryMap Map<String, Object> queryMap);
}

同样的, 也可以通过使用QueryMapEncoder实现用POJO对象生成查询参数(query parameter):

public interface Api {
  @RequestLine("GET /find")
  V find(@QueryMap CustomPojo customPojo);
}

当用这种方式时, 如果没有指定一个自定义的QueryMapEncoder, 那么查询参数的(query parameter)内容将根据对象的成员变量生成, 参数名对应变量名. 下面的例子中, 根据POJO对象生成的查询参数(query parameter)的内容是"/find?name={name}&number={number}", 生成的查询参数的顺序是不固定的, 按照惯例, 如果POJO对象的某个变量值为null, 那么该变量会被丢弃.

public class CustomPojo {
  private final String name;
  private final int number;

  public CustomPojo (String name, int number) {
    this.name = name;
    this.number = number;
  }
}

设置自定义QueryMapEncoder的方式如下:

public class Example {
  public static void main(String[] args) {
    MyApi myApi = Feign.builder()
                 .queryMapEncoder(new MyCustomQueryMapEncoder())
                 .target(MyApi.class, "https://api.hostname.com");
  }
}

当用@QueryMao注解时, 默认的编码器(encoder)会对对象的字段使用反射来将其展开成查询参数(query string). 如果希望通过对象的getter和setter方法来展开查询参数(query string), 请使用BeanQueryMapEncoder:

public class Example {
  public static void main(String[] args) {
    MyApi myApi = Feign.builder()
                 .queryMapEncoder(new BeanQueryMapEncoder())
                 .target(MyApi.class, "https://api.hostname.com");
  }
}

Error Handling

你可以通过在Feign实例构造时注册一个自定义的ErrorDecoder来实现对非正常响应的控制:

public class Example {
  public static void main(String[] args) {
    MyApi myApi = Feign.builder()
                 .errorDecoder(new MyErrorDecoder())
                 .target(MyApi.class, "https://api.hostname.com");
  }
}

所有HTTP状态码不为2xx的响应都会触发ErrorDecoderdecode方法, 在这个方法内你可以对这些响应针对性地抛出异常, 或做其他额外的处理. 如果希望对请求进行重试, 那么可以抛出RetryableException, 该异常会触发Retryer.

Retry

默认情况下, Feign会对产生IOException的请求自动重试, 无论使用的是哪种HTTP方法, 都认为IOExcdeption是由短暂的网络问题产生的. 对ErrorDecoder内抛出的RetryableException也会进行请求重试. 你也可以通在Feign实例构造时设置自定义的Retryer来定制重试行为:

public class Example {
  public static void main(String[] args) {
    MyApi myApi = Feign.builder()
                 .retryer(new MyRetryer())
                 .target(MyApi.class, "https://api.hostname.com");
  }
}

Retryer的实现需要决定一个请求是否应该进行重试, 可以通过continueOrPropagate(RetryableException e)方法的返回值(truefalse)来实现. 每个Feign客户端执行时都会构造一个Retryer实例, 这样的话你可以维护每个请求的重新状态.

如果最终重试也失败了, 那么会抛出RetryException, 如果希望抛出导致重试失败的异常, 可以在构造Feign客户端时指定exceptionPropagationPolicy()选项.

Static and Default Methods

使用Feign的接口可能是静态的或默认的方法(Java 8及以上支持), 这允许Feign客户端包含一些不适用底层接口定义的逻辑. 例如, 使用静态方法可以很轻易地指定通用客户端构造配置, 使用默认方法可以用于组合查询或定义默认参数:

interface GitHub {
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);

  @RequestLine("GET /users/{username}/repos?sort={sort}")
  List<Repo> repos(@Param("username") String owner, @Param("sort") String sort);

  default List<Repo> repos(String owner) {
    return repos(owner, "full_name");
  }

  /**
   * Lists all contributors for all repos owned by a user.
   */
  default List<Contributor> contributors(String user) {
    MergingContributorList contributors = new MergingContributorList();
    for(Repo repo : this.repos(owner)) {
      contributors.addAll(this.contributors(user, repo.getName()));
    }
    return contributors.mergeResult();
  }

  static GitHub connect() {
    return Feign.builder()
                .decoder(new GsonDecoder())
                .target(GitHub.class, "https://api.github.com");
  }
}

疯狂的爱因斯坦
1.3k 声望37 粉丝